package com.linkafri.generate.code.service;

import com.alibaba.fastjson.JSONObject;
import com.linkafri.generate.code.bean.*;
import com.linkafri.generate.code.constants.Constants;
import com.linkafri.generate.code.util.Utils;
import com.linkafri.generate.commom.bean.Result;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.sql.*;
import java.util.*;

/** Created by Rocky on xxxx-xx-xx. */
@Service
@Slf4j
public class BaseServiceImpl implements BaseService {
  @Autowired DataSource dataSource;

  @Value("${spring.datasource.driverClassName}")
  String driverClassName;

  @Value("${file.dir}")
  String fileDir;

  Connection connection = null;

  @Override
  public Result<List<Schema>> getAllSchema() throws SQLException {
    Result<List<Schema>> listResult = new Result<>();
    try {
      if (driverClassName.contains("post")) {
        connection = dataSource.getConnection();
        ResultSet resultSet =
            connection
                .prepareStatement("SELECT datname as SCHEMA_NAME FROM pg_database;")
                .executeQuery();
        ArrayList<Schema> schemas = new ArrayList<>();
        while (resultSet.next()) {
          String schemaName = resultSet.getString(Constants.schemaName);
          Schema schema = new Schema();
          schema.setName(schemaName);
          schemas.add(schema);
        }
        schemas.removeAll(Constants.EXCLUDE_SCHEMA_LIST);
        listResult.setValue(schemas);
        listResult.setSuccess(true);
      } else {
        connection = dataSource.getConnection();
        ResultSet resultSet =
            connection
                .prepareStatement("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA;")
                .executeQuery();
        ArrayList<Schema> schemas = new ArrayList<>();
        while (resultSet.next()) {
          String schemaName = resultSet.getString(Constants.schemaName);
          Schema schema = new Schema();
          schema.setName(schemaName);
          schemas.add(schema);
        }
        schemas.removeAll(Constants.EXCLUDE_SCHEMA_LIST);
        listResult.setValue(schemas);
        listResult.setSuccess(true);
      }

    } catch (Exception e) {
      e.printStackTrace();
      log.error(e.getMessage() + "获取所有数据库名称失败");
    } finally {
      if (connection != null) {
        connection.close();
      }
    }

    return listResult;
  }

  @Override
  public Result<List<Table>> getAllTableNameBySchema(Schema schema) {
    Connection con = null;
    Result<List<Table>> tableResult = new Result<>();

    try {
      if (driverClassName.contains("post")) {
        con = dataSource.getConnection();
        con.setCatalog(schema.getName());

        Statement stmt = con.createStatement();
        ResultSet rs =
            stmt.executeQuery("SELECT   tablename   FROM   pg_tables where schemaname='public';");
        List<Table> tables = new ArrayList<>();
        while (rs.next()) {
          Table table = new Table();
          table.setName(rs.getString(1));
          tables.add(table);
        }
        tableResult.setSuccess(true);
        tableResult.setValue(tables);
      } else {
        con = dataSource.getConnection();
        con.setCatalog(schema.getName());
        Statement stmt = con.createStatement();
        ResultSet rs = stmt.executeQuery("SHOW TABLES; ");
        List<Table> tables = new ArrayList<>();
        while (rs.next()) {
          Table table = new Table();
          table.setName(rs.getString(1));
          tables.add(table);
        }
        tableResult.setSuccess(true);
        tableResult.setValue(tables);
      }

    } catch (Exception e) {
      log.error(e.getMessage() + "获取所有表名称失败");
    }
    return tableResult;
  }

  @Override
  public Result<Table> getTableBySchemaAndTable(Schema schema, Table table, BaseBean baseBean) {
    Connection con = null;
    PreparedStatement stmt;
    Result<Table> tableResult = new Result<>();
    try {
      con = dataSource.getConnection();
      con.setCatalog(schema.getName());
      String sql = "";
      if (driverClassName.contains("post")) {

        sql = "SELECT * FROM public." + table.getName().toUpperCase();
      } else if (driverClassName.contains("mysql")) {
        sql = "SELECT * FROM " + table.getName().toUpperCase();
      } else {
        sql = "SELECT * FROM " + table.getName().toUpperCase();
      }

      stmt = con.prepareStatement(sql);
      ResultSet rs;
      if (driverClassName.contains("post")) {

        rs = stmt.executeQuery();
      } else if (driverClassName.contains("mysql")) {
        rs = stmt.executeQuery(sql);
      } else {
        rs = stmt.executeQuery(sql);
      }

      ResultSetMetaData data = rs.getMetaData();
      for (int i = 1; i <= data.getColumnCount(); i++) {
        String columnName = data.getColumnName(i);

        String columnClassName1 = data.getColumnClassName(i);
        String columnTypeName = data.getColumnTypeName(i);
        String columnClassName = data.getColumnClassName(i);
        if (baseBean != null) {
          if (columnName.equals(baseBean.getPkName())) {
            String[] split = columnClassName.split("\\.");
            baseBean.setPkClassType(split[split.length - 1]);
            baseBean.setPkJDBCType(Utils.getRealJDBCTypr(columnTypeName.toUpperCase()));
          }
        }

        Column column = new Column();
        column.setName(columnName);
        column.setTypeName(columnTypeName);
        column.setClassName(columnClassName);
        table.getColumns().add(column);
      }
      tableResult.setValue(table);
      tableResult.setSuccess(true);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (con != null) {
        try {
          con.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }

    return tableResult;
  }

  @Override
  public Result<BaseBean> getTableByBaseBean(BaseBean baseBean) {
    Result<BaseBean> baseBeanResult = new Result<>();
    try {
      Table table = new Table();
      table.setName(baseBean.getTableName());
      Schema schema1 = new Schema();
      schema1.setName(baseBean.getSchemaName());
      // 处理主键
      Connection connection = dataSource.getConnection();
      PreparedStatement ps =
          connection.prepareStatement(
              "SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME=?  and CONSTRAINT_NAME='PRIMARY';");
      ps.setString(1, baseBean.getTableName());
      ResultSet pkRes = ps.executeQuery();
      while (pkRes.next()) {

        String pkName = pkRes.getString(2);

        baseBean.setPkName(pkName);
      }

      Result<Table> tableBySchemaAndTable = getTableBySchemaAndTable(schema1, table, baseBean);
      Table table2 = tableBySchemaAndTable.getValue();
      if (driverClassName.contains("post")) {
        baseBean.setDriverClassName("postgresql");
      } else if (driverClassName.contains("mysql")) {
        baseBean.setDriverClassName("mysql");
      } else {
        log.error("this sql maybe has error");
      }
      Set<String> imports = baseBean.getImports();

      List<Field> fields = baseBean.getFields();
      if (fields.size() != 0) {
        fields = new ArrayList<>();
      }
      if (imports.size() != 0) {
        imports = new TreeSet<>();
      }
      List<Column> columns = table2.getColumns();
      // 判断逻辑删除字段时候存在
      Boolean hasDelName = false;
      for (Column column : columns) {
        if (column.getClassName().equals("java.sql.Date")) {
          imports.add("java.util.Date");
          continue;
        }
        String columnName = column.getName();
        if (columnName.equals(baseBean.getPkName())) {
          baseBean.setPkClassName(Utils.transferColumnToField(columnName));
          baseBean.setPkClassNameFirstUp(
              Utils.firstUpCase(Utils.transferColumnToField(columnName)));
        }
        imports.add(column.getClassName());
        Field field = new Field();
        field.setName(Utils.transferColumnToField(columnName));
        field.setType(Utils.getType(column.getClassName()));
        if (Utils.transferColumnToField(columnName)
            .equals(Utils.transferColumnToField(baseBean.getPkName()))) {
          baseBean.setIdType(field.getType());
        }
        if (columnName.equalsIgnoreCase(baseBean.getCurrentIsDelete())) {
          hasDelName = true;
        }
        field.setColumnName(columnName);
        field.setJdbcType(Utils.getRealJDBCTypr(column.getTypeName().toUpperCase()));
        fields.add(field);
      }
      if (!(hasDelName || StringUtils.isEmpty(baseBean.getCurrentIsDelete()))) {
        return null;
      }
      // 覆盖上一个值
      baseBean.setFields(fields);
      baseBean.setImports(imports);
      baseBeanResult.setSuccess(true);
      baseBeanResult.setValue(baseBean);

      // 第一步：实例化Freemarker的配置类
      Configuration conf = new Configuration();
      // 第二步：给配置类设置路径
      String dir = Thread.currentThread().getContextClassLoader().getResource("ftl/").getPath();

      baseBean.setCreateTime(Utils.getNowTime());
      conf.setDirectoryForTemplateLoading(new File(dir));

      System.out.println(JSONObject.toJSONString(baseBean));

      File ftlDIR = new File(dir);
      baseBean.setClassName(
          Utils.tableNameToClassName(baseBean.getTableName(), baseBean.getRemoveEntityPrefix()));
      baseBean.setClassNameStack(
          Utils.firsLowerCase(
              Utils.tableNameToClassName(
                  baseBean.getTableName(), baseBean.getRemoveEntityPrefix())));
      // pkClassNameStack
      File[] files = ftlDIR.listFiles();

      for (File file : files) {
        String targetPath = fileDir + "/";

        Template template = conf.getTemplate(file.getName());
        Writer out;

        if (file.getName().contains("XML")) {
          targetPath += Utils.pointToUrlStr(baseBean.getMapperXMLPackageName()) + "/";
        } else if (file.getName().equals("Mapper.ftl")) {
          targetPath += Utils.pointToUrlStr(baseBean.getMapperXMLPackageName()) + "/";
        } else if (file.getName().contains("Bean")) {
          targetPath += Utils.pointToUrlStr(baseBean.getEntityPackageName()) + "/";
        } else if (file.getName().equals("Controller.ftl")) {
          targetPath += Utils.pointToUrlStr(baseBean.getCurdPackageName()) + "/controller/";
        } else if (file.getName().contains("Dao.ftl")) {
          targetPath += Utils.pointToUrlStr(baseBean.getCurdPackageName()) + "/dao/";
        } else if (file.getName().contains("DaoImpl.ftl")) {
          targetPath += Utils.pointToUrlStr(baseBean.getCurdPackageName()) + "/dao/impl/";
        } else if (file.getName().contains("Service.ftl")) {
          targetPath += Utils.pointToUrlStr(baseBean.getCurdPackageName()) + "/service/";
        } else if (file.getName().contains("ServiceImpl.ftl")) {
          targetPath += Utils.pointToUrlStr(baseBean.getCurdPackageName()) + "/service/impl/";
        } else {
          targetPath += Utils.pointToUrlStr(baseBean.getCurdPackageName()) + "/";
        }
        File targetFile = new File(targetPath);
        if (!targetFile.exists()) {
          targetFile.delete();
          targetFile.mkdirs();
        }
        if (file.getName().contains("XML")) {
          File fileXML =
              new File(targetPath + baseBean.getClassName() + baseBean.getMapperSuffix() + ".xml");
          log.info(fileXML.getPath());
          out = new FileWriter(fileXML);
        } else {
          String subFTL = file.getName().substring(0, file.getName().length() - 4);
          if (subFTL.equals("Bean")) {
            subFTL = "";
          }
          File fileJava = new File(targetPath + baseBean.getClassName() + subFTL + ".java");
          log.info(fileJava.getPath());
          out = new FileWriter(fileJava);
        }

        Map<String, Object> root = new HashMap<>();

        root.put("baseBean", baseBean);
        template.process(root, out);
        out.flush();
        out.close();
      }

    } catch (Exception e) {
      e.printStackTrace();
      log.error(e.getMessage());
    }

    return baseBeanResult;
  }
}
